//
//  TestEngine.cpp
//  CollectionTest
//
//  Created by Dusan Vujosevic on 2013-10-25.
//  Copyright (c) 2013 Suavetech. All rights reserved.
//

#include "TestEngine.h"
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <sys/time.h>

class CodeTimer
{
public:
	CodeTimer()
	{
		_startTime = getMicroTime();
	}
	
	uint64_t getTimeUs() const
	{
		return getMicroTime() - _startTime;
	}
	
	uint64_t getTimeMs() const
	{
		return getTimeUs() / 1000;
	}

	static uint64_t getMicroTime()
	{
		struct timeval tp;
		memset(&tp, 0, sizeof(tp));
		if(gettimeofday(&tp, NULL) == 0)
			return (((long long)tp.tv_sec) * 1000000) + tp.tv_usec;
		else
			return 0;
	}
	
private:
	uint64_t _startTime;
};

TestEngine::TestEngine()
{
	// All of this could be inline but I don't care about speed here
	_testLoadElementsCount = 100;
	_testSearchElementsCount = 100;
	_loadTestBytes = NULL;
	_searchTestBytes = NULL;

	_loadElementsCount = 1000000;
	_searchElementsCount = 1000000;
	_loadBytes = NULL;
	_searchBytes = NULL;
}

TestEngine::~TestEngine()
{
	clearBuffers();
}

void TestEngine::clearBuffers()
{
	delete _loadTestBytes;
	_loadTestBytes = NULL;
	delete _searchTestBytes;
	_searchTestBytes = NULL;
	delete _loadBytes;
	_loadBytes = NULL;
	delete _searchBytes;
	_searchBytes = NULL;
}

bool TestEngine::init()
{
	// Load the stuff for the functional test
	{
		_loadTestBytes = new bufType[testLoadBytesSize()];
		elemType* testArea = (elemType*)_loadTestBytes;
		for(elemType u = 0; u < _testLoadElementsCount; u++)
		{
			*testArea++ = (elemType)u;
		}
		_searchTestBytes = new bufType[testLoadBytesSize()];
		elemType* searchArea = (elemType*)_searchTestBytes;
		for(elemType u = 0; u < _testLoadElementsCount / 2; u++)
		{
			*searchArea++ = (elemType)u;
		}
		for(elemType u = _testLoadElementsCount; u < _testLoadElementsCount * 3 / 2; u++)
		{
			*searchArea++ = (elemType)u;
		}
	}
	
	// Load the stuff for the speed test
	{
		// Open /dev/urandom
		const char* urandomPath = "/dev/urandom";
		
		int urandomFd = open(urandomPath, O_RDONLY);
		if(-1 == urandomFd)
		{
			fprintf(stderr, "Can't open %s: %i\n", urandomPath, errno);
			return false;
		}
		
		// Pull the random bytes for element pool
		{
			size_t loadBytes = loadBytesSize();
			_loadBytes = new bufType[loadBytes];
			if(!fillBytes(urandomFd, _loadBytes, loadBytes))
			{
				fputs("Couldn't load the element pool", stderr);
				close(urandomFd);
				return false;
			}
		}
		
		// Pull the random bytes for search pool
		{
			size_t searchBytes = searchBytesSize();
			_searchBytes = new bufType[searchBytes];
			if(!fillBytes(urandomFd, _searchBytes, searchBytes))
			{
				fputs("Couldn't load the search pool", stderr);
				close(urandomFd);
				return false;
			}
		}
		
		(void) close(urandomFd);
		_dataUsage.SetBaseline();
	}
	return true;
}

bool TestEngine::runFunctionalTest(CollectionBase* collection)
{
	{
		fprintf(stdout, "*Loading collection (%zu elements)*\n", _testLoadElementsCount);
		collection->loadElements(_loadTestBytes, _testLoadElementsCount);
	}
	
	{
		fprintf(stdout, "*Random search test (%zu elements)*\n", _testSearchElementsCount);
		elemType* searchBuf = (elemType*)_searchTestBytes;
		size_t foundElemCount = 0;
		for(size_t elem = 0; elem < _testSearchElementsCount; elem++)
		{
			if(collection->findElement(*searchBuf++))
				foundElemCount++;
		}
		if(foundElemCount == _testLoadElementsCount / 2)
			return true;
		else
			return false;
	}
}

bool TestEngine::runTest(CollectionBase* collection)
{
	fprintf(stdout, "***Starting test for collection: %s***\n", collection->name());
	if(!collection->init())
	{
		fprintf(stderr, "Failed to init\n");
		return false;
	}
	
	fprintf(stdout, "**Functional test for collection: %s**\n", collection->name());
	if(runFunctionalTest(collection))
		fputs("Functional test succeeded\n", stdout);
	else
		fputs("Functional test failed\n", stdout);
	
	
	fprintf(stdout, "**Speed test for collection: %s**\n", collection->name());
	collection->reset(); // reset after functional test
	bool testSuccess = doRunTest(collection);
	if(testSuccess)
		fputs("Speed test succeeded\n", stdout);
	else
		fputs("Speed test failed\n", stdout);
		
	uint64_t dataUsageKb = _dataUsage.DataUsage();
	fprintf(stdout, "Data usage: %llu MB\n", dataUsageKb / 1024);
	
	fputs("\n\n", stdout);
	
	return testSuccess;
}

bool TestEngine::doRunTest(CollectionBase* collection)
{	
	{
		fprintf(stdout, "*Loading collection (%zu elements)*\n", _loadElementsCount);
		CodeTimer t;
		collection->loadElements(_loadBytes, _loadElementsCount);
		fprintf(stdout, "Load took: %llu ms\n", t.getTimeMs());
	}
	
	{
		fprintf(stdout, "*Random search test (%zu elements)*\n", _searchElementsCount);
		CodeTimer t;
		elemType* searchBuf = (elemType*)_searchBytes;
		size_t foundElemCount = 0;
		for(size_t elem = 0; elem < _searchElementsCount; elem++)
		{
			if(collection->findElement(*searchBuf++))
				foundElemCount++;
		}
		fprintf(stdout, "Random search took: %llu ms, %zu hits\n", t.getTimeMs(), foundElemCount);
	}
	
	if(collection->canIterateOrdered())
	{
		fputs("*Ordered iteration test*\n", stdout);
		CodeTimer t;
		collection->resetIterateOrdered();
		elemType nextElem;
		for(size_t elem = 0; elem < _loadElementsCount; elem++)
		{
			if(!collection->nextIterateOrdered(&nextElem))
			{
				fputs("Iteration failed", stderr);
				return false;
			}
		}
		fprintf(stdout, "Iteration took: %llu ms\n", t.getTimeMs());
	}

	collection->printStats();
	
	return true;
}

bool TestEngine::fillBytes(int fd, bufType* buf, size_t bufSize)
{
	const size_t defReadSize = 4096;

	size_t loadedBytes = 0;
	while(loadedBytes < bufSize)
	{
		size_t bytesToRead = defReadSize;
		if(bytesToRead + loadedBytes > bufSize)
			bytesToRead = bufSize - loadedBytes;
		
		ssize_t readBytes = read(fd, buf + loadedBytes, bytesToRead);
		if(readBytes != bytesToRead)
		{
			fprintf(stderr, "Failed to read from fd %i: %i", fd, errno);
			return false;
		}
		loadedBytes += readBytes;
	}
	return true;
}


